package com.leal.util;

import com.leal.elasticsearch.ESMapping;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Classname bigdata
 * @Description EsMappingsUtil
 * @Date 2023/7/5 18:54
 * @Created by leal
 */
public class EsMappingsUtil {
    public static List<ESMapping> getFieldInfoByFile() {
        File file = new File("/Users/lidongyue/Works/bigdata/hive-module/src/main/resources/esMapping.txt");
        List<ESMapping> fieldInfoList = new ArrayList<>();
        try {
            String line = null;
            BufferedReader reader = new BufferedReader(new FileReader(file));
            Map<String, List<ESMapping>> complexFieldInfoMap = new HashMap<>();
            while ((line = reader.readLine()) != null) {
                String[] fields = line.split(",");
                //严格按照4个配置
                if (fields.length == 4) {
                    // 如果第一个字段不为0 为复合类型
                    ESMapping esFieldMappingInfo = new ESMapping(fields[1], fields[2], "1".equals(fields[3]));
                    if (!"0".equals(fields[0])) {
                        //判断hashmap 是否存在某个key,存在即添加元素，否则增加key
                        complexFieldInfoMap.computeIfAbsent(fields[0], k -> new ArrayList<>()).add(esFieldMappingInfo);
                    } else {
                        // 如果第一个字段为0则为普通类型
                        fieldInfoList.add(esFieldMappingInfo);
                    }
                }
                //如果字段数小于4个，则忽略
                else {
                    System.out.println("字段数小于4个，忽略");
                }
            }
            //遍历fieldInfoList，如果类型为复合类型，添加complexFieldInfoMap
            for (ESMapping esFieldMappingInfo : fieldInfoList) {
                switch (esFieldMappingInfo.getFieldType()) {
                    case "object":
                    case "nested":
                        esFieldMappingInfo.setComplexField(complexFieldInfoMap.get(esFieldMappingInfo.getFieldName()));
                        break;
                }
            }
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fieldInfoList;
    }

    public static String getMapping(List<ESMapping> fieldInfoList) {
        //遍历fieldInfoList 判断其类型
        StringBuilder sb = new StringBuilder();
        for (ESMapping esFieldMappingInfo : fieldInfoList) {
            switch (esFieldMappingInfo.getFieldType()) {
                case "string":
                    sb.append(getStringMapping(esFieldMappingInfo.getFieldName(), esFieldMappingInfo.getParticiple())).append(",");
                    break;
                case "date":
                    sb.append(getDateMapping(esFieldMappingInfo.getFieldName())).append(",");
                    break;
                case "object":
                case "nested":
                    sb.append("\"")
                            .append(esFieldMappingInfo.getFieldName())
                            .append("\":{\"type\":\"")
                            .append(esFieldMappingInfo.getFieldType())
                            .append("\",\"properties\":{")
                            .append(getMapping(esFieldMappingInfo.getComplexField()))
                            .append("}}")
                            .append(",");
                    break;
                default:
                    // 数字和其他类型
                    sb.append(getNumberMapping(esFieldMappingInfo.getFieldName(), esFieldMappingInfo.getFieldType())).append(",");
            }
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }
    // 以下根据不同的mapping创建索引

    /**
     * 用于索引全文值的字段。使用文本数据类型的字段，它们会被分词，在索引之前将字符串转换为单个术语的列表(倒排索引)，
     * 分词过程允许ES搜索每个全文字段中的单个单词。文本字段不用于排序，很少用于聚合（重要的术语聚合是一个例外）。
     * 什幺情况适合使用text datatype，
     * 只要不具备唯一性的字符串一般都可以使用text，例如：电子邮件正文，商品介绍，个人简介
     *
     * @param field        es 字段
     * @param isParticiple 是否分词
     * @return mapping
     * @throws IOException
     */
    public static String getStringMapping(String field, boolean isParticiple) {
        /**
         * analyzer：指明该字段用于索引时和搜索时的分析字符串的分词器（使用search_analyzer可覆盖它）。 默认为索引分析器或标准分词器
         * fielddata：指明该字段是否可以使用内存中的fielddata进行排序，聚合或脚本编写？默认值为false，可取值true或false。（排序，分组需要指定为true）
         * fields：【多数类型】text类型字段会被分词搜索，不能用于排序，而当字段既要能通过分词搜索，又要能够排序，就要设置fields为keyword类型进行聚合排序。
         * index：【是否被索引】设置该字段是否可以用于搜索。默认为true，表示可以用于搜索。
         * search_analyzer：设置在搜索时，用于分析该字段的分析器，默认是【analyzer】参数的值。
         * search_quote_analyzer：设置在遇到短语搜索时，用于分析该字段的分析器，默认是【search_analyzer】参数的值。
         * index_options：【索引选项】用于控制在索引过程中哪些信息会被写入到倒排索引中
         * - docs：只索引文档号到倒排索引中，但是并不会存储
         * - freqs：文档号和关键词的出现频率会被索引，词频用于给文档进行评分，重复词的评分会高于单个次评分
         * - positions：文档号、词频和关键词 term 的相对位置会被索引，相对位置可用于编辑距离计算和短语查询(不分词那种)
         * - offsets：文档号、词频、关键词 term 的相对位置和该词的起始字符串偏移量
         */
        return isParticiple ? "\"" + field + "\":{\"type\":\"text\",\"analyzer\": \"ik_max_word\"}"
                :
                /**
                 * keyword datatype，关键字数据类型，用于索引结构化内容的字段。使用keyword类型的字段，其不会被分析，给什幺值就原封不动地按照这个值索引，
                 * 所以关键字字段只能按其确切值进行搜索，通常用于过滤、排序和聚合。什幺情况下使用keyword datatype，具有唯一性的字符串，
                 * 例如：电子邮件地址、MAC地址、身份证号、状态代码…
                 *
                 * eager_global_ordinals：指明该字段是否加载全局序数？默认为false，不加载。 对于经常用于术语聚合的字段，启用此功能是个好主意。
                 * fields：指明能以不同的方式索引该字段相同的字符串值，例如用于搜索的一个字段和用于排序和聚合的多字段
                 * ignore_above：不要索引长于此值的任何字符串。默认为2147483647，以便接受所有值
                 * index：指明该字段是否可以被搜索，默认为true，表示可以被搜索
                 * index_options：指定该字段应将哪些信息存储在索引中，以便用于评分。默认为docs，但也可以设置为freqs，这样可以在计算分数时考虑术语频率
                 * norms：在进行查询评分时，是否需要考虑字段长度，默认为false，不考虑
                 * ignore_above：默认值是256，该参数的意思是，当字段文本的长度大于指定值时，不会被索引，
                 * 但是会存储。即当字段文本的长度大于指定值时，聚合、全文搜索都查不到这条数据。
                 * ignore_above 最大值是 32766 ，但是要根据场景来设置，比如说中文最大值 应该是设定在10922
                 */
                "\"" + field + "\":{\"type\":\"keyword\"}";

    }

    public static String getDateMapping(String field) {
        return "\"" + field + "\":{\"type\":\"date\",\"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis\"}";
    }

    /**
     * 数字类型
     * ES支持的数字类型有整型数字类型，
     * integer类型、long类型、short类型、byte类型。
     * 浮点型数字类型 double类型、 float类型、 half_float类型、 scaled_float类型这类数据类型都是以确切值索引的，
     * 可以使用term查询精确匹配。
     * <p>
     * long带符号的64位整数，最小值-263，最大值263-1
     * integer带符号的32位整数，最小值-231，最大值231^-1
     * short带符号的16位整数，最小值-32768，最大值32767
     * byte带符号的8位整数，最小值-128，最小值127
     * double双精度64位IEEE 754 浮点数
     * float单精度32位IEEE 754 浮点数
     * half_float半精度16位IEEE 754 浮点数
     * scaled_float带有缩放因子的缩放类型浮点数，依靠一个long数字类型通过一个固定的(double类型)缩放因数进行缩放
     *
     * @param field
     * @param numberType
     * @return
     */
    public static String getNumberMapping(String field, String numberType) {
        return "\"" + field + "\":{\"type\":\"" + numberType + "\"}";
    }

    /**
     * 复合类型
     * > 数组类型
     * 默认情况下，任何字段都可以包含零个或多个值，当包含多个值时，它就表示array datatype了。
     * 但是，数组中的所有值必须具有相同的数据类型(要幺同为字符串，要幺同为整型，不能数组中一些值为字符串，另一些值为整型)
     * ES从数组中的第一个值确定字段类型，所有后续值必须具有相同的数据类型，或者必须至少可以将后续值强制转换为相同的数据类型。
     * 使用array datatype，不需要预先配置，它们是开箱即用的
     * 使用_mapping查看索引的映射类型时，array datatype不会被写出来，还是以数组的元素的基本类型来表示
     * > 对象类型
     * type='object'
     * 一个文档的一个属性可以是一个内部对象，而且，这个内部对象，可以再包含一个内部对象…(可以有多层嵌套)
     * 整个外部文档是一个JSON对象
     * 在ES内部，这种文档会被索引成一种简单平坦的键值对列表(平铺)
     * > 嵌套类型
     * 当数组里面嵌套对象的时候就为nested类型，需要指定type='nested' 否则只认为是一般的复合
     *
     * @param typeName   类型名称
     * @param type       复合类型，数组的一般类型、object、nested, 这里只处理nested和object
     *                   默认情况下ES会把JSON对象直接映射为object类型，只有手动设置才会映射为nested类型
     * @param filedArray 具体的字段
     * @return
     */
    public static String getCompositeMapping(List<ESMapping> filedArray, String typeName, String type) {
        System.out.println("filedArray = " + filedArray);
        StringBuilder stringBuilder = new StringBuilder();
        for (ESMapping field : filedArray) {
            // check mapping type
            stringBuilder.append(getFieldMapping(field.getFieldName(), field.getFieldType(),
                    field.getParticiple(), null));
        }
        return "{\"" + typeName + "\":{\"type\":\"" + type + "\",\"properties:\"{" + stringBuilder + "}}}";
    }

    public static String getFieldMapping(String field, String type, boolean isParticiple, List<ESMapping> fieldMappingInfos) {
        StringBuilder stringBuilder = new StringBuilder();
        switch (type) {
            case "string":
                stringBuilder.append(getStringMapping(field, isParticiple)).append(",");
                break;
            case "date":
                stringBuilder.append(getDateMapping(field)).append(",");
                break;
            case "nested":
            case "object":
                stringBuilder.append(getCompositeMapping(fieldMappingInfos, field, type)).append(",");
                break;
            default:
                stringBuilder.append(getNumberMapping(field, type)).append(",");
        }
        //删除最后一个字符串
        return stringBuilder.deleteCharAt(stringBuilder.length() - 1).toString();
    }

}
